/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.cardarus.operatingsystem.model;

import com.cardarus.operatingsystem.domain.Certificate;
import com.cardarus.operatingsystem.domain.Customer;
import com.cardarus.operatingsystem.domain.HolidayPeriod;
import com.cardarus.operatingsystem.domain.Item;
import com.cardarus.operatingsystem.domain.Payment;
import com.cardarus.operatingsystem.domain.Reminder;
import com.cardarus.operatingsystem.domain.ReminderRegistration;
import com.cardarus.operatingsystem.domain.RewardMembership;
import com.cardarus.operatingsystem.domain.SalesTransaction;
import com.cardarus.operatingsystem.domain.SalesTransactionRow;
import com.cardarus.operatingsystem.domain.SystemUser;
import com.cardarus.operatingsystem.domain.Userrol;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

/**
 * The business model of the system.
 * All business rules are implemented in this class.
 * This class is responsible to read from or save to the data base.
 * Implements the Singleton Pattern.
 *
 * @author Javier Snaider
 */
public class BusinessModel {

    private static final Logger logger = Logger.getLogger(BusinessModel.class.getCanonicalName());
    /**
     * the Singleton instance
     */
    private static BusinessModel instance = new BusinessModel();
    /**
     * This EntityManagerFactory provides EntityManagers to control persistence
     * using JPA 2.0
     */
    private EntityManagerFactory emf = null;

    /**
     * To get the Singleton instance
     * @return the Singleton instance
     */
    public static BusinessModel getInstance() {
        return instance;
    }
    private static final int MAX_REMINDERS = 20;
    private static final double TAX_RATE = .1;

    /**
     * The constructor of the class is private to implement Singleton pattern.
     */
    private BusinessModel() {
        emf = Persistence.createEntityManagerFactory("CardArUsOS-warPU");
    }

    /**
     * to get a EntityManager from the factory.
     * @return a EntityManager from the factory.
     */
    public EntityManager getEntityManager() {
        return emf.createEntityManager();
    }

    /**
     * Creates a new Reminder.
     * @param reminder
     */
    public void createReminder(Reminder reminder) {
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Customer customer = reminder.getCustomer();
            if (customer != null) {
                customer = em.getReference(customer.getClass(), customer.getId());
                reminder.setCustomer(customer);
            }
            em.persist(reminder);
            if (customer != null) {
                customer.getReminders().add(reminder);
                customer = em.merge(customer);
            }
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }

        logger.log(Level.FINE, "Reminder created: {0}", reminder);
    }

    /**
     * Finds a Customer from id.
     * @param id the id of the Customer.
     * @return the Customer or null if not present
     */
    public Customer findCustomer(int id) {
        EntityManager em = null;
        Customer customer = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            customer = em.find(Customer.class, id);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return customer;
    }

    /**
     * Gets a SystemUser by username
     * @param username
     * @return the SystemUser or null if not present
     */
    public SystemUser getUserByUsername(String username) {
        EntityManager em = null;
        SystemUser user = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();

            Query q = em.createQuery("select c from SystemUser c where c.userName = :username");
            q.setParameter("username", username);
            user = (SystemUser) q.getSingleResult();
            em.getTransaction().commit();
        } catch (Exception e) {
            user = null;
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return user;

    }

    /**
     * returns true or false to identify whether registration is successful.
     * @param pin
     * @return true or false to identify whether registration is successful.
     */
    public Boolean registerReward(Customer customer) {
        EntityManager em = null;
        boolean ok = true;
        try {
            em = getEntityManager();
            em.getTransaction().begin();


            if (customer != null && !customer.isRegisteredForRewards()) {
                //em.merge(customer);
                Customer c = em.find(Customer.class, customer.getId());
                RewardMembership rm = new RewardMembership();
                rm.setBalance(100);
                rm.setCustomer(c);
                em.merge(rm);

                c.setRegisteredForRewards(true);
                c.setPointsAccount(rm);
                em.merge(c);

                customer.setRegisteredForRewards(true);
                customer.setPointsAccount(rm);

                logger.log(Level.FINE, "Customer register for reward: {0}", customer);

            } else {
                ok = false;
            }

            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return ok;
    }

    /**
     * returns true or false to identify whether registration is successful.
     * @param pin
     * @return true or false to identify whether registration is successful.
     */
    public Boolean registerRemind(Customer customer) {

        EntityManager em = null;
        boolean ok = true;
        try {
            em = getEntityManager();
            em.getTransaction().begin();

            if (customer != null && customer.getReminderRegistration() == null) {

                Customer c = em.find(Customer.class, customer.getId());
                ReminderRegistration rr = new ReminderRegistration();
                rr.setCustomer(c);
                rr.setMaxReminders(MAX_REMINDERS);
                rr.setRegistrationDate(new Date());
                c.setReminderRegistration(rr);

                em.merge(rr);
                em.merge(c);

                logger.log(Level.FINE, "Customer register for remind: {0}", customer);

            } else {
                ok = false;
            }

            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return ok;
    }

    /**
     * Gets an Item by its id
     * @param id the id of the Item to find.
     * @return the Item o null if not present
     */
    public Item getItem(int id) {
        EntityManager em = null;
        Item item = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            item = em.find(Item.class, id);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return item;
    }

    public List<Item> getsItem(int itemId) {

        EntityManager em = null;
        List<Item> stock = null;

        try {
            em = getEntityManager();
            em.getTransaction().begin();

            Query q = em.createQuery("SELECT s from Item s WHERE s.id = :itemID");
            q.setParameter("itemID", itemId);
            stock = q.getResultList();
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }

        return stock;
    }

    /**
     * Gets a Customer by PIN
     * @param pin the Customer's PIN
     * @return the Customer
     */
    public Customer getCustomerByPin(String pin) {
        EntityManager em = null;
        Customer customer = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();

            Query q = em.createQuery("select c from Customer c where c.pin = :pin");
            q.setParameter("pin", pin);

            customer = (Customer) q.getSingleResult();
            em.getTransaction().commit();
        } catch (Exception e) {
            customer = null;
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return customer;

        //   return new Customer((int) (Math.random() * 1000), "Joe", "Doe", "(901) 111-1111", "joe@aaa.com", "550 Central Av., Memphis TN", "joe", "pass", pin, false, new Date(0L));
    }

    /**
     * Returns a list of reminders for Customer with PIN = pin
     * @param pin the PIN of the Customer
     * @return The list of reminders
     */
    public List<Reminder> getReminders(Customer user) {

        EntityManager em = null;
        List<Reminder> reminders = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();

            Query q = em.createQuery("select r from Reminder r where r.customer = :user order by r.reminderDate");
            q.setParameter("user", user);
            reminders = q.getResultList();
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return reminders;
    }

    /**
     *  Creates a new user in the system
     * @param customer the user to create
     * @return true if the user was created
     */
    public boolean createNewCustomer(Customer customer) {

        customer.setPin(createRndPin());
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            em.merge(customer);
            Userrol ur = new Userrol();
            ur.setRol("user");
            ur.setUsername(customer.getUserName());
            em.merge(ur);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return true;
    }

    public boolean createNewUser(SystemUser user, Userrol role) {

        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            em.merge(user);
            em.merge(role);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return true;
    }

    /**
     *
     * @return a list of all the items
     */
    public List<Item> getStock() {

        EntityManager em = null;
        List<Item> stock = null;

        try {
            em = getEntityManager();
            em.getTransaction().begin();

            Query q = em.createQuery("SELECT s from Item s ORDER BY s.id");
            stock = q.getResultList();
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }

        return stock;

    }

    /**
     * Creates a new Item in the stock
     * @param StockID
     * @param StockName
     * @param StockDescription
     * @param StockPrice
     * @param StockInventory
     * @return
     */
    public boolean addNewStock(Item newStock) {
        EntityManager em = null;
        boolean isSuccess = true;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Item item = em.find(Item.class, newStock.getId());
            if (item != null) {
                isSuccess = false;
            } else {
                em.persist(newStock);
                em.getTransaction().commit();
            }
        } finally {
            if (em != null) {
                em.close();
            }
        }
        logger.log(Level.FINE, "Stock Item Created: {0}", newStock);
        return isSuccess;


    }

    /**
     * Returns the number of used remiders for Customer with PIN pin.
     * @param pin
     * @return the number of used remiders
     */
    public int getUsedReminders(Customer user) {
        return getReminders(user).size();
    }

    /**
     * Returns the number of total remiders for Customer with PIN pin.
     * @param pin
     * @return the number of total remiders
     */
    public int getTotalReminders(Customer user) {
        return 20;


    }

    /**
     * Updates the Item
     * @param ID
     * @param name
     * @param description
     * @param price
     * @param inventory
     */
    public void updateItem(Item it) {
        EntityManager em = null;

        try {
            em = getEntityManager();
            em.getTransaction().begin();
            em.merge(it);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }

        logger.log(Level.FINE, "Stock Item Updated: {0}", it.getId());
    }

    /**
     * Delete a list of reminders
     * @param pin the user pin
     * @param reminders the list of reminders
     * @param selectedReminders the selected reminders
     * @return
     */
    public boolean deleteReminders(String[] selectedReminders) {

        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();

            for (String remId : selectedReminders) {
                try {
                    int id = Integer.parseInt(remId);
                    Reminder r = em.find(Reminder.class, id);
                    if (r != null) {
                        em.remove(r);
                    }
                } catch (NumberFormatException e) {
                    logger.log(Level.WARNING, "Error deleting reminder id:{0}", remId);
                }
            }
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return true;
    }

    private String createRndPin() {
        String pin = "";
        for (int i = 0; i < 10; i++) {
            pin = pin + ((int) (Math.random() * 10));
        }
        return pin;
    }

    public List<SalesTransaction> getTransactionsByDate(Date startDate, Date endDate) {
        List<SalesTransaction> transactions = new ArrayList<SalesTransaction>();

        /*  // get transactions from db
        SalesTransaction s= new SalesTransaction();
        s.setId(1);
        s.setDate(new Date());
        s.setSubTotal(100);
        s.setTax(10);
        s.setTotal(110);

        transactions.add(s);
         *
         */

        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();

            Query q = em.createQuery("SELECT t from SalesTransaction t where t.saleDate >= :start and  t.saleDate <= :end  ORDER BY t.id");
            q.setParameter("start", startDate);
            q.setParameter("end", endDate);
            transactions = q.getResultList();
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }


        return transactions;

    }

    /**
     * Create a new Holiday Period.
     * @param hp
     */
    public void createHolidayPeriod(HolidayPeriod hp) {
        EntityManager em = null;

        try {
            em = getEntityManager();
            em.getTransaction().begin();
            em.persist(hp);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
            logger.log(Level.FINE, "Holiday created: {0}", hp);
        }
    }

    /**
     * Returns a list of the HolidayPeriod defined in the system.
     * @return a list of the HolidayPeriod defined in the system.
     */
    public List<HolidayPeriod> getHolidays() {

        EntityManager em = null;
        List<HolidayPeriod> holidays = null;

        try {
            em = getEntityManager();
            em.getTransaction().begin();

            Query q = em.createQuery("SELECT h from HolidayPeriod h ORDER BY h.startDate");
            holidays = q.getResultList();
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }

        return holidays;
    }

    public boolean deleteHolidays(String[] selectedHolidays) {
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();

            for (String remId : selectedHolidays) {
                try {
                    int id = Integer.parseInt(remId);
                    HolidayPeriod h = em.find(HolidayPeriod.class, id);
                    if (h != null) {
                        em.remove(h);
                    }
                } catch (NumberFormatException e) {
                    logger.log(Level.WARNING, "Error deleting holiday id:{0}", remId);
                }
            }
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return true;
    }

    /**
     * Returns total available points for a given user
     * @param user
     * @return
     */
    public int getUserPoint(Customer user) {
        EntityManager em = null;
        int points = 0;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            user = em.find(Customer.class, user.getId());
            RewardMembership rm = user.getPointsAccount();
            if (rm != null) {
                points = user.getPointsAccount().getBalance();
            }
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return points;
    }

    public boolean isRewardUser(Customer user) {

        return user.isRegisteredForRewards();
    }

    public Certificate getCertificate(int id) {
        EntityManager em = null;
        Certificate certificate = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            certificate = em.find(Certificate.class, id);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return certificate;
    }

    public boolean validateCertificate(Certificate certificate, Customer customer, double total) {
        boolean valid = false;

        if (certificate != null && customer != null) {
            valid = (certificate.getExpiration().after(new Date())
                    && customer.getId() == certificate.getCustomer().getId())
                    && certificate.getMinimumPurchase() <= total;
        }
        return valid;
    }

    public void payment(SalesTransaction sale) {
        EntityManager em = null;


        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Certificate certificate = sale.getCertificate();
            Customer customer = sale.getCustomer();
            if (customer != null) {
                em.merge(customer);
                if (customer.isRegisteredForRewards()) {
                    RewardMembership rm = customer.getPointsAccount();

                    if (rm != null) {

                        rm.setBalance(rm.getBalance() + sale.getPointsEarned() - sale.getPointsUsed());
                        em.merge(rm);
                        //TODO create points transactions
                    }
                }
            }
            for (SalesTransactionRow row : sale.getRows()) {
                Item item = row.getItem();

                item.setInventory(item.getInventory() - row.getQuantity());
                em.merge(item);
            }
            if (certificate != null) {
                certificate = em.find(Certificate.class, certificate.getId());
                certificate.setUsed(true);
                sale.setCertificates(certificate);
            }
            em.persist(sale);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
            logger.log(Level.FINE, "sale transaction registered: {0}", sale.getId());
        }
    }

    public int calculateEarnedPoints(SalesTransaction sale) {

        int points = 0;

        // 10 point for each $1
        points = points + (int) (sale.getSubTotal() * 10);


        if (!isHolidayPeriod()) { // if it is NOT a Holiday period
            //25 points for each card
            for (SalesTransactionRow row : sale.getRows()) {
                if (row.getItem().isCard()) {
                    points = points + 25 * row.getQuantity();
                }
            }
        } else { // if it is a Holiday period
            //100 points for 3 or more cards
            for (SalesTransactionRow row : sale.getRows()) {
                int totalCards = 0;
                if (row.getItem().isCard()) {
                    totalCards += row.getQuantity();
                }
                if (totalCards >= 3) {
                    points = points + 100;
                }
            }
        }
        // 50 points for each especial box
        for (SalesTransactionRow row : sale.getRows()) {
            if (row.getItem().isSpecialBox()) {
                points = points + 50 * row.getQuantity();
            }
        }
        sale.setPointsEarned(points);
        return points;
    }

    private boolean isHolidayPeriod() {
        EntityManager em = null;
        List<HolidayPeriod> holidays = null;

        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Query q = em.createQuery("SELECT h from HolidayPeriod h where h.startDate <= :today and h.endDate >= :today ORDER BY h.startDate");
            q.setParameter("today", getToday());
            holidays = q.getResultList();
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
        return (holidays.size() >= 1);
    }

    public Date getToday() {
        return getToday(0);
    }

    public Date getToday(int days) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date());
        Calendar calnew = Calendar.getInstance();
        calnew.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH));
        calnew.add(Calendar.DAY_OF_MONTH, days);
        return calnew.getTime();
    }


    public double pointsToDolars(int points) {
        return points / 300;
    }

    /**
     * Given the SystemUser Id, modify the information of the SystemUser information
     * store in the sysusr and usrrol
     * @param userid
     * @param sysusr
     * @param usrrol 
     */
    public void editSystemUserInfo(int userid, SystemUser sysusr, Userrol usrrol) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * Get all the existed SystemUser and put them into a List
     * @return 
     */
    public List<SystemUser> getAllSystemUser() {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * Given the SystemUser Id, get the SystemUser information
     * Put it into a List
     * @param id
     * @return 
     */
    public List<SystemUser> getsSystemUserByID(int id) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * Returns the correct number of points to use in payment
     * @param inputPoints
     * @param customer
     * @return
     */
    public int verifyPoints(int inputPoints, Customer customer) {
        inputPoints = (inputPoints / 300) * 300;
        int points = customer.getPointsAccount().getBalance();
        if (points > inputPoints) {
            return inputPoints;
        }
        return 0;
    }

    public void calculateTotal(SalesTransaction sale) {
        if (sale != null) {
            sale.setTax(sale.getNetSubTotal() * TAX_RATE);
            sale.setTotal(sale.getNetSubTotal() + sale.getTax());
        }
    }

    public boolean verifyCard(Payment payment) {

        if (payment.getCardDetail().startsWith("0", MAX_REMINDERS)) {
            return false;
        }
        payment.setAuthorizationCode(createRndPin());
        return true;
    }

    public void printReceipt(SalesTransaction sale) {
        PrintWriter printer = null;
        try {
            printer = new PrintWriter("c:\\printout\\receipt" + sale.getId() + ".txt");
            printer.println("Card Ar US");
            printer.printf("Date: %tD %n", sale.getDate());
            printer.printf("Receipt: %08d %n", sale.getId());
            printer.println();
            Customer customer = sale.getCustomer();

            if (customer != null && customer.isRegisteredForRewards()) {
                printer.printf("Customer# %s points: %2d %n", customer.getPin(),
                        sale.getPointsEarned());
            }

            for (SalesTransactionRow row : sale.getRows()) {
                Item it = row.getItem();
                printer.printf("%08d %10s %5.2f x %2d %5.2f %n", it.getId(), it.getDescription().substring(0, 10),
                        row.getUnitPrice(), row.getQuantity(), row.getPrice());
            }

            printer.printf("SubTotal            %5.2f %n", sale.getSubTotal());
            if (sale.getCertificate() != null) {
                printer.printf("Certificate %05d  %5.2f %n", sale.getCertificate().getId(),
                        -sale.getCertificate().getDenomination());
            }

            if (sale.getPointsUsed() != 0) {
                printer.printf("Used Points %05d  %5.2f %n", sale.getPointsUsed(),
                        -sale.getDollarsFromPoints());
            }
            printer.printf("NetTotal           %5.2f %n", sale.getNetSubTotal());
            printer.printf("Tax                %5.2f %n", sale.getTax());
            printer.printf("Total              %5.2f %n", sale.getTotal());

        } catch (Exception ex) {
            logger.log(Level.SEVERE, null, ex);
        } finally {
            if (printer != null) {
                printer.close();
            }
        }
        logger.log(Level.INFO, "Recipt printed");
    }

    public void processCertificates() {
         EntityManager em = null;
        List<Customer> customers = null;

        try {
            em = getEntityManager();
            em.getTransaction().begin();

            Query q = em.createQuery("SELECT c from Customer c where c.registeredForRewards=true ORDER BY c.id");
            customers = q.getResultList();

            for(Customer customer:customers){
                RewardMembership rm =customer.getPointsAccount();
                int certDenom = rm.getBalance()/1500;
                if(certDenom>0){
                    Certificate cert = new Certificate(certDenom * 5.0,customer,
                            getToday(),getToday(60),0);
                    em.persist(cert);
                    rm=em.merge(rm);
                    rm.setBalance(rm.getBalance()-certDenom);
                    rm=em.merge(rm);
                    
                    logger.log(Level.INFO, "Email Certificate {0} to customer {1} email:{2}",
                            new Object[]{cert.getId(), customer.getUserName(), customer.getEmail()});
                }
            }
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

}
